#include <iostream>
#include <string>
#include <memory>
#include <map>

class TreeNode: public std::enable_shared_from_this<TreeNode> {
public:
    enum NodeType {
        Leaf,
        Branch
    };
    TreeNode(std::string name) : name(name), type(Branch) {}
    TreeNode(std::string name, std::string value) : name(name) {
        setValue(value);
    }

    void setValue(const std::string& v);
    std::string getValue();
    void addChild(std::shared_ptr<TreeNode> child);
    void removeChild(std::string childName);
    void printTree(int level = 0);
    std::string getPath() const;
    std::shared_ptr<TreeNode> getChild(std::string childName) const;
    std::shared_ptr<TreeNode> getParent() const;
private:
    NodeType type;
    std::string name;
    std::string value;
    std::weak_ptr<TreeNode> parent;
    std::map<std::string, std::shared_ptr<TreeNode>> children;
};

void TreeNode::setValue(const std::string& v) {
    value = v;
    type = Leaf;
}

std::string TreeNode::getValue() {
    return value;
}

/**
 * Add a child node
 * @param child child node
 */
void TreeNode::addChild(std::shared_ptr<TreeNode> child) {
    child->parent = shared_from_this();
    children[child->name] = child;
    type = Branch;
}

/**
 * Remove a child node
 * @param childName name of the child node
 */
void TreeNode::removeChild(std::string childName) {
    children.erase(childName);
}

/**
 * Print the tree
 * @param level level of the node
 */
void TreeNode::printTree(int level) {
    for (int i = 0; i < level; i++) {
        std::cout << "  ";
    }
    // print the name and optionally the value
    std::cout << name << ": ";
    if (type == Leaf) {
        std::cout << value << std::endl;
    } else {
        std::cout << std::endl;
    }
    for (auto& child : children) {
        child.second->printTree(level + 1);
    }
}

/**
 * Get a child node by name
 * @param childName name of the child node
 * @return child node
 */
std::shared_ptr<TreeNode> TreeNode::getChild(std::string childName) const {
    auto child = children.find(childName);
    if (child == children.end()) {
        return nullptr;
    }
    return child->second;
}

/**
 * Get the path of a node
 * @return path
 */
std::string TreeNode::getPath() const {
    std::string path = name;
    auto parent = getParent();
    while (parent) {
        path = parent->name + "/" + path;
        parent = parent->getParent();
    }
    return path;
}

/**
 * Get the parent node
 * @return parent node
 */
std::shared_ptr<TreeNode> TreeNode::getParent() const {
    return parent.lock();
}

class Tree {
public:
    Tree(std::shared_ptr<TreeNode> root) : root(root) {}
    void printTree() {
        root->printTree();
    }
private:
    std::shared_ptr<TreeNode> root;
};

int main(int, char**){
    // create a tree with root "config"
    std::shared_ptr<TreeNode> root = std::make_shared<TreeNode>("config");
    Tree tree(root);

    // add children to the root node
    root->addChild(std::make_shared<TreeNode>("database"));
    root->addChild(std::make_shared<TreeNode>("server"));

    // add children to the "database" node
    auto dbNode = root->getChild("database");
    dbNode->addChild(std::make_shared<TreeNode>("host", "127.0.0.1"));
    dbNode->addChild(std::make_shared<TreeNode>("username", "admin"));
    dbNode->addChild(std::make_shared<TreeNode>("port", "3306"));

    // print the tree
    tree.printTree();
    std::cout << "\nPath of 'port' node: " << dbNode->getChild("port")->getPath() << std::endl;
    return 0;
}
